package cn.springcloud.fix.oauth2.autoconfigure.authserver;

import java.util.Arrays;
import java.util.Collections;
import java.util.UUID;

import cn.springcloud.fix.oauth2.autoconfigure.client.OAuth2ClientProperties;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.oauth2.config.annotation.builders.ClientDetailsServiceBuilder;
import org.springframework.security.oauth2.config.annotation.builders.InMemoryClientDetailsServiceBuilder;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerEndpointsConfiguration;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.client.BaseClientDetails;
import org.springframework.security.oauth2.provider.token.AccessTokenConverter;
import org.springframework.security.oauth2.provider.token.TokenStore;

@Configuration
@ConditionalOnClass(EnableAuthorizationServer.class)
@ConditionalOnBean(AuthorizationServerEndpointsConfiguration.class)//@EnableAuthorizationServer 会 Import AuthorizationServerEndpointsConfiguration
@EnableConfigurationProperties(AuthorizationServerProperties.class)
@Import(AuthorizationServerTokenServicesConfiguration.class)
public class OAuth2AuthorizationServerConfiguration {

    private static final Log logger = LogFactory.getLog(OAuth2AuthorizationServerConfiguration.class);

    @Configuration
    @ConditionalOnMissingBean(AuthorizationServerConfigurer.class)
    private static class AuthorizationSecurityConfigurer extends AuthorizationServerConfigurerAdapter {

        private final BaseClientDetails details;

        private final AuthenticationManager authenticationManager;

        private final TokenStore tokenStore;

        private final AccessTokenConverter tokenConverter;

        private final AuthorizationServerProperties properties;

        public AuthorizationSecurityConfigurer(BaseClientDetails details,
                                               AuthenticationConfiguration authenticationConfiguration,
                                               ObjectProvider<TokenStore> tokenStore,
                                               ObjectProvider<AccessTokenConverter> tokenConverter,
                                               AuthorizationServerProperties properties) throws Exception {

            this.details = details;
            this.authenticationManager = authenticationConfiguration.getAuthenticationManager();
            this.tokenStore = tokenStore.getIfAvailable();
            this.tokenConverter = tokenConverter.getIfAvailable();
            this.properties = properties;
        }

        @Override
        public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
            ClientDetailsServiceBuilder<InMemoryClientDetailsServiceBuilder>.ClientBuilder builder = clients.inMemory().withClient(this.details.getClientId());
            builder.secret(this.details.getClientSecret())
                    .resourceIds(this.details.getResourceIds().toArray(new String[0]))
                    .authorizedGrantTypes(this.details.getAuthorizedGrantTypes().toArray(new String[0]))
                    .authorities(AuthorityUtils.authorityListToSet(this.details.getAuthorities()).toArray(new String[0]))
                    .scopes(this.details.getScope().toArray(new String[0]));

            if (this.details.getAutoApproveScopes() != null) {
                builder.autoApprove(this.details.getAutoApproveScopes().toArray(new String[0]));
            }
            if (this.details.getAccessTokenValiditySeconds() != null) {
                builder.accessTokenValiditySeconds(this.details.getAccessTokenValiditySeconds());
            }
            if (this.details.getRefreshTokenValiditySeconds() != null) {
                builder.refreshTokenValiditySeconds(this.details.getRefreshTokenValiditySeconds());
            }
            if (this.details.getRegisteredRedirectUri() != null) {
                builder.redirectUris(this.details.getRegisteredRedirectUri().toArray(new String[0]));
            }
        }

        @Override
        public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
            if (this.tokenConverter != null) {
                endpoints.accessTokenConverter(this.tokenConverter);
            }
            if (this.tokenStore != null) {
                endpoints.tokenStore(this.tokenStore);
            }
            if (this.details.getAuthorizedGrantTypes().contains("password")) {
                endpoints.authenticationManager(this.authenticationManager);
            }
        }

        @Override
        public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
            security.passwordEncoder(NoOpPasswordEncoder.getInstance());
            if (this.properties.getCheckTokenAccess() != null) {
                security.checkTokenAccess(this.properties.getCheckTokenAccess());
            }
            if (this.properties.getTokenKeyAccess() != null) {
                security.tokenKeyAccess(this.properties.getTokenKeyAccess());
            }
            if (this.properties.getRealm() != null) {
                security.realm(this.properties.getRealm());
            }
        }

        @Configuration
        protected static class ClientDetailsLogger implements InitializingBean {

            private final OAuth2ClientProperties oAuth2ClientProperties;

            protected ClientDetailsLogger(OAuth2ClientProperties oAuth2ClientProperties) {
                this.oAuth2ClientProperties = oAuth2ClientProperties;
            }

            public void afterPropertiesSet() {
                init();
            }

            public void init() {
                String prefix = "security.oauth2.client";
                boolean defaultSecret = this.oAuth2ClientProperties.isDefaultSecret();
                logger.info(String.format("Initialized OAuth2 Client%n%n%s.client-id = %s%n" + "%s.client-secret = %s%n%n",
                        prefix, this.oAuth2ClientProperties.getClientId(), prefix,
                        defaultSecret ? this.oAuth2ClientProperties.getClientSecret() : "****"));
            }
        }

        @Configuration
        @ConditionalOnMissingBean(BaseClientDetails.class)
        protected static class BaseClientDetailsConfiguration {

            private final OAuth2ClientProperties client;

            protected BaseClientDetailsConfiguration(OAuth2ClientProperties client) {
                this.client = client;
            }

            @Bean
            @ConfigurationProperties(prefix = "security.oauth2.client")
            public BaseClientDetails oauth2ClientDetails() {
                BaseClientDetails details = new BaseClientDetails();
                if (this.client.getClientId() == null) {
                    this.client.setClientId(UUID.randomUUID().toString());
                }
                details.setClientId(this.client.getClientId());
                details.setClientSecret(this.client.getClientSecret());
                details.setAuthorizedGrantTypes(Arrays.asList("authorization_code", "password", "client_credentials", "implicit", "refresh_token"));
                details.setAuthorities(AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_USER"));
                details.setRegisteredRedirectUri(Collections.emptySet());
                return details;
            }
        }
    }
}
